package com.ibm.risk.irmp.workflow.client.flowable.service;

import com.ibm.risk.irmp.common.auth.AuthUtils;
import com.ibm.risk.irmp.common.workflow.EnumAction;
import com.ibm.risk.irmp.workflow.client.flowable.RestVarConvertor;
import com.ibm.risk.irmp.workflow.client.flowable.api.*;
import com.ibm.risk.irmp.workflow.client.flowable.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class WorkflowService {

    public static final String ACTION_PARM = "ACTION";
    public static final String COMMENT_PARM = "COMMENT";
    @Autowired
    private ProcessInstancesApi procInstApi;
    @Autowired
    private TasksApi tasksApi;
    @Autowired
    private HistoryTaskApi historyTaskApi;
    @Autowired
    private ProcessInstanceVariablesApi procVarApi;
    @Autowired
    private HistoryProcessApi historyProcessApi;

    private static ProcessResult toProcessResult(String procId, List<RestVariable> restVariableList) {
        Map<String, Object> vars = RestVarConvertor.convertToMap(restVariableList);
        return new ProcessResult(procId, vars);
    }

    public ProcessResult startProcess(String processDefKey, String businessKey, Map<String, Object> variables) {
        List<RestVariable> restVariables = RestVarConvertor.toRestVariables(variables);
        if (variables != null && !variables.containsKey(ACTION_PARM)) {
            restVariables.add(new RestVariable().name(ACTION_PARM).value(EnumAction.SAVE.name()));
        }
        restVariables.add(new RestVariable().name("currentUser").value(AuthUtils.getLoginUsername()));
        ProcessInstanceCreateRequest creatReq = new ProcessInstanceCreateRequest();
        creatReq.setProcessDefinitionKey(processDefKey);
        creatReq.setVariables(restVariables);
        creatReq.setBusinessKey(businessKey);
        log.info("starting process: {} for {}", processDefKey, businessKey);
        ProcessInstanceResponse response = procInstApi.createProcessInstance(creatReq);
        log.debug("stated process: {}", response);
        List<RestVariable> variables1 = procVarApi.listProcessInstanceVariables(response.getId(), null);
        log.trace("variables: {}", variables1);
        return toProcessResult(response.getId(), variables1);
    }

    public void assignTask(String procInstId, String taskName, String assignee) {
        TaskQueryRequest query = new TaskQueryRequest();
        query.setProcessInstanceId(procInstId);
        query.setName(taskName);
        DataResponseTaskResponse response = tasksApi.queryTasks(query);
        List<TaskResponse> tasks = response.getData();
        log.debug("found {} tasks for process instance {}", tasks.size(), procInstId);
        if (tasks.size() == 1) {
            TaskResponse task = tasks.get(0);
            String taskId = task.getId();
            TaskActionRequest body = new TaskActionRequest();
            body.setAction("claim");
            body.setAssignee(assignee);
            log.info("assigning task {}/{} to {}", taskId, taskName, assignee);
            tasksApi.executeTaskAction(taskId, body);
        }
        log.warn("found {} tasks for process instance {}", tasks.size(), procInstId);
        throw new RuntimeException("Not exactly one task [" + tasks.size() + "] found for process instance: " + procInstId);
    }

    public ProcessResult executeProcess(String procInstId, EnumAction action, String comment, Map<String, Object> variables) {
        Map<String, Object> lvars = null;
        if (!StringUtils.isBlank(comment)) {
            lvars = Map.of(COMMENT_PARM, comment);
        }
        return executeProcess(procInstId, action, variables, lvars);
    }

    public ProcessResult executeProcess(String procInstId, EnumAction action, Map<String, Object> variables, Map<String, Object> localVariables) {
        if (procInstId == null) {
            throw new RuntimeException("Process instance ID is required");
        }
        TaskQueryRequest query = new TaskQueryRequest();
        query.setProcessInstanceId(procInstId);
        String userId = AuthUtils.getLoginUsername();
        query.assignee(userId);
        log.debug("querying for tasks for process instance {} on {}", procInstId, userId);
        DataResponseTaskResponse response = tasksApi.queryTasks(query);
        List<TaskResponse> tasks = response.getData();
        log.debug("found {} tasks for process instance {}", tasks.size(), procInstId);
        if (tasks.size() == 1) {
            TaskResponse task = tasks.get(0);
            String taskId = task.getId();
            TaskActionRequest actionRequest = new TaskActionRequest();
            actionRequest.setAction("complete");
            List<RestVariable> vars = new ArrayList<>();
            vars.add(new RestVariable().name("currentUser").value(AuthUtils.getLoginUsername()));
            vars.addAll(RestVarConvertor.toRestVariables(variables));
            if (localVariables != null) {
                vars.addAll(RestVarConvertor.toRestVariables(localVariables, "local"));
            }
            vars.add(new RestVariable().name(ACTION_PARM).value(action.name()));
            vars.add(new RestVariable().name(ACTION_PARM).value(action.name()).scope("local"));
            actionRequest.setVariables(vars);
            tasksApi.executeTaskAction(taskId, actionRequest);
            HistoricProcessInstanceQueryRequest req = new HistoricProcessInstanceQueryRequest();
            req.processInstanceId(procInstId).includeProcessVariables(true);
            DataResponseHistoricProcessInstanceResponse resp = historyProcessApi.queryHistoricProcessInstance(req);
            List<RestVariable> procVars = resp.getData().get(0).getVariables();
            log.trace("process variables: {}", procVars);
            return toProcessResult(procInstId, procVars);
        } else {
            if (tasks.size() > 1) {
                log.warn("More than one task found for process instance {}: tasks: {}", procInstId, tasks);
                throw new RuntimeException("More than one task found for process instance " + procInstId);
            }
            log.warn("No task found for process instance {}", procInstId);
            throw new RuntimeException("No task found for process instance " + procInstId);
        }
    }

    public ProcessResult executeProcess(String procInstId, EnumAction action, String comment) {
        Map<String, Object> lvars = StringUtils.isEmpty(comment) ? null : Map.of(COMMENT_PARM, comment);
        return executeProcess(procInstId, action, Map.of(), lvars);
    }

    public List<ProcessActionVo> getHistoryActionList(String procInstId) {

        HistoricTaskInstanceQueryRequest body = new HistoricTaskInstanceQueryRequest();
        body.processInstanceId(procInstId).finished(true).includeTaskLocalVariables(true);
        body.sort("endTime");
        DataResponseHistoricTaskInstanceResponse response = historyTaskApi.queryHistoricTaskInstance(body);
        List<ProcessActionVo> vos = new ArrayList<>(response.getData().size());
        response.getData().forEach(task -> {
            List<RestVariable> variables = task.getVariables();
            log.trace("variables: {}", variables);
            Map<String, Object> map = RestVarConvertor.convertToMap(variables);
            if (map.containsKey(ACTION_PARM)) {
                vos.add(new ProcessActionVo(task.getName(), (String) map.get(ACTION_PARM), (String) map.get(COMMENT_PARM),
                        task.getAssignee(), task.getEndTime()));
            }
        });
        return vos;
    }

    public byte[] getProcDiagram(String procInstId) {
        return procInstApi.getProcessInstanceDiagram(procInstId);
    }
}
